package com.gete.api;

import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gete.dto.PredictInfoDto;
import io.gate.gateapi.ApiClient;
import io.gate.gateapi.ApiException;
import io.gate.gateapi.GateApiException;
import io.gate.gateapi.api.FuturesApi;
import io.gate.gateapi.api.SpotApi;
import io.gate.gateapi.api.WalletApi;
import io.gate.gateapi.models.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Service
public class GateApi implements GateApiService{
    private static final String API_KEY = "0ee9b6df2d968f5cdbeba0621f9a3d20";
    private static final String SECRET_KEY = "37bdf0acc1a9d19f53145bb3336285e4c6d92fc4165a28bda2f383e91ba67459";

    @Override
    public synchronized void trade() {
        StopWatch stopWatch =  new StopWatch();
        stopWatch.start();
        try {
            String currencyPair = "SATS_USDT";
            String currency = currencyPair.split("_")[1];
            String buyCurrency = currencyPair.split("_")[0];

            // Initialize API client
            ApiClient client = new ApiClient();
            client.setApiKeySecret(API_KEY, SECRET_KEY);
            // Setting basePath is optional. It defaults to https://api.gateio.ws/api/v4
            client.setBasePath("https://api.gateio.ws/api/v4");

            SpotApi spotApi = new SpotApi(client);
            CurrencyPair pair = spotApi.getCurrencyPair(currencyPair);
            log.info("testing against currency pair: {}" , currencyPair);
            String minAmount = pair.getMinQuoteAmount();

            // get last price
            List<Ticker> tickers = spotApi.listTickers().currencyPair(currencyPair).execute();
            assert tickers.size() == 1;
            String lastPrice = tickers.get(0).getLast();
            assert lastPrice != null;

            // make sure balance is enough

            //账户购买可用资金量
            List<SpotAccount> accounts = spotApi.listSpotAccounts().currency(currency).execute();
            assert accounts.size() == 1;
            BigDecimal available = new BigDecimal(accounts.get(0).getAvailable());
            log.info("Account available: {} currency {}", available.toPlainString(), currency);
            if(available.compareTo(new BigDecimal("10")) < 0 ){
                log.info("当前的余额币种：{} 数量：{} 太低,停止运行",currency,available);
                return;
            }

            //账户持仓币种量
            List<SpotAccount> accountsBuyCurrency = spotApi.listSpotAccounts().currency(buyCurrency).execute();
            assert accountsBuyCurrency.size() == 1;
            BigDecimal availableBuyCurrency = new BigDecimal(accountsBuyCurrency.get(0).getAvailable());
            Map<String, BigDecimal> buyAndSellMap = calculateTradingRatioAmout(lastPrice, available, availableBuyCurrency);


            //动态平衡买入和卖出的比例
            BigDecimal buyAmount = available.divide(new BigDecimal(lastPrice),10, RoundingMode.DOWN).divide(buyAndSellMap.get("buyAmout"),0, RoundingMode.DOWN);
            BigDecimal sellAmount =  available.divide(new BigDecimal(lastPrice),10, RoundingMode.DOWN).divide(buyAndSellMap.get("sellAmout"),0, RoundingMode.DOWN);


            log.info("设置买入和卖出数量buyAmount:{}sellAmount:{}",buyAmount,sellAmount);


            BigDecimal upperPrice = new BigDecimal(lastPrice).multiply(BigDecimal.valueOf(1.03)).setScale(10, RoundingMode.HALF_UP);
            BigDecimal lowerPrice = new BigDecimal(lastPrice).multiply(BigDecimal.valueOf(0.98)).setScale(10, RoundingMode.HALF_UP);
            BigDecimal furturePrice = new BigDecimal(lastPrice).multiply(BigDecimal.valueOf(1.05)).setScale(10, RoundingMode.HALF_UP);

            //计算1u多少数量
            BigDecimal oneUsdtSell = new BigDecimal(1).divide(upperPrice,0,RoundingMode.HALF_UP);
            log.info("计算1u={}卖出数量币种",oneUsdtSell);
            BigDecimal oneUsdtBuy = new BigDecimal(1).divide(lowerPrice,0,RoundingMode.HALF_UP);
            log.info("计算1u={}买入数量币种",oneUsdtBuy);

            if(buyAmount.compareTo(oneUsdtBuy)<0){
                buyAmount = oneUsdtBuy;
                log.info("买入数量低于最低的数量，替换为1U的币种数量");
            }
            if(sellAmount.compareTo(oneUsdtSell)<0){
                log.info("卖出的数量低于最低的数量，替换为1U的币种数量");
                sellAmount = oneUsdtSell;
            }

            List<Order> allPairOrder = spotApi.listOrders(currencyPair, Order.StatusEnum.OPEN.toString()).execute();
            if(!CollectionUtils.isEmpty(allPairOrder)){

                // 按照价格分组，分组按照价格从低到高排序
                TreeMap<String, List<Order>> groupedOrders = allPairOrder.stream()
                        .collect(Collectors.groupingBy(Order::getPrice, TreeMap::new, Collectors.toList()));

                // 打印结果
                groupedOrders.forEach((price, orderList) -> {
                    log.info("Price: {} 数量：{}" , price,orderList.size());
                });
                log.info("当前挂单总数：{}个",allPairOrder.size());
                List<Order> collectHaveOrder = allPairOrder.stream().filter(price -> new BigDecimal(price.getPrice()).compareTo(upperPrice) <= 0
                        && new BigDecimal(price.getPrice()).compareTo(lowerPrice) >= 0).collect(Collectors.toList());
                //重复的价格挂的去掉
                List<Order> collectRepear = allPairOrder.stream().filter(price -> new BigDecimal(price.getPrice()).compareTo(lowerPrice) == 0
                        || new BigDecimal(price.getPrice()).compareTo(upperPrice) == 0).collect(Collectors.toList());
                log.info("重复价格数量{}",collectRepear.size());
                if((!CollectionUtils.isEmpty(collectHaveOrder) && collectHaveOrder.size()>0) || collectRepear.size()>0){
                    log.info("当前币种{}，当前价格区间：upperPrice{}-lowerPrice{}存在订单orderNo{}，不需要创建订单",currencyPair,new BigDecimal(String.valueOf(upperPrice)).toPlainString(),new BigDecimal(String.valueOf(lowerPrice)).toPlainString(),collectHaveOrder.stream().map(k->k.getId()).collect(Collectors.joining(",")));
                    return;
                }
            }
            log.info("需要创建订单了，当前价格:{} 上:{}下:{}不存在订单数",lastPrice,upperPrice,lowerPrice);

            List<Order> batchOrder = new ArrayList<>();
            // create spot order
            Order orderBuy  = placeOrder(buyAmount,new BigDecimal(String.valueOf(lowerPrice)).toPlainString(),Order.SideEnum.BUY,currencyPair);
            log.info("place a spot{} order in {} with amount {} and price {}", orderBuy.getSide(), orderBuy.getCurrencyPair(),
                    orderBuy.getAmount(), orderBuy.getPrice());


            batchOrder.add(orderBuy);
            Order orderSell  = placeOrder(sellAmount,new BigDecimal(String.valueOf(upperPrice)).toPlainString(),Order.SideEnum.SELL,currencyPair);
            log.info("place a spot {} order in {} with amount {} and price {}", orderSell.getSide(), orderSell.getCurrencyPair(),
                    orderSell.getAmount(), orderSell.getPrice());


            //创建高价订单卖出
            BigDecimal futureOrderAmount = (buyAmount.subtract(sellAmount)).multiply(BigDecimal.valueOf(0.95).setScale(10, RoundingMode.HALF_UP));
            if(futureOrderAmount.compareTo(new BigDecimal(0))>0){
                log.info("创建高价低于市场价格百分之20订单,订单数量{},订单金额{}",futureOrderAmount,furturePrice);
                Order orderSellFuture  = placeOrder(futureOrderAmount,new BigDecimal(String.valueOf(furturePrice)).toPlainString(),Order.SideEnum.SELL,currencyPair);
                batchOrder.add(orderSellFuture);
            }

            batchOrder.add(orderSell);

            List<Order> collectAmoutOrder = batchOrder.stream().filter(k -> new BigDecimal(k.getAmount()).multiply(new BigDecimal(k.getPrice())).setScale(10, RoundingMode.HALF_DOWN).
                    compareTo(new BigDecimal(1)) <= 0).collect(Collectors.toList());
            if(!CollectionUtils.isEmpty(collectAmoutOrder)){
                log.info("订单金额低于1USDT,拒绝提交订单");
                return;
            }
            List<BatchOrder> batchOrders = spotApi.createBatchOrders(batchOrder);
            log.info("order created with id {}, status {}", batchOrders.get(0).getId(), batchOrders.get(0).getStatus());
            log.info("order created with id {}, status {}", batchOrders.get(1).getId(), batchOrders.get(1).getStatus());
        }catch (Exception e){
            log.info("执行异常{}",e.getMessage());
        }finally {
            stopWatch.stop();
            log.info("执行一次任务总计耗时{}:ms", stopWatch.getTotalTimeMillis());
        }

    }



    private Order placeOrder(BigDecimal orderAmount,String lastPrice,Order.SideEnum side,String currencyPair) {
        Order order = new Order();
        order.setAccount(Order.AccountEnum.SPOT);
        order.setAutoBorrow(false);
        order.setTimeInForce(Order.TimeInForceEnum.GTC);
        order.setType(Order.TypeEnum.LIMIT);
        order.setAmount(orderAmount.toPlainString());
        order.setPrice(lastPrice);
        order.setSide(side);
        order.setCurrencyPair(currencyPair);
        order.setText(("t-batchOrder-"+UUID.randomUUID()).substring(0,20));
        return order;
    }

    private static Map<String,BigDecimal> calculateTradingRatioAmout(String lastPrice,BigDecimal availableMoney,BigDecimal availableBuyCurrency) {
        BigDecimal availableBuyCurrencyTotal = new BigDecimal(lastPrice).multiply(availableBuyCurrency).setScale(2, RoundingMode.HALF_UP);
        BigDecimal total = availableMoney.add(availableBuyCurrencyTotal).setScale(2, RoundingMode.HALF_UP);

        Double chipPercentage = availableBuyCurrencyTotal.divide(total,2, RoundingMode.DOWN).doubleValue();
        log.info("当前交易对总资金{}，当前交易对持有市值{}筹码占当前资金比例{}",total,availableBuyCurrencyTotal,chipPercentage);
        Map<String,BigDecimal> buyAndSell = new ConcurrentHashMap<>();
      /*  if (chipPercentage > 0.7) {
            // 筹码占比大于90%，卖出2份，买入1份
            BigDecimal buy = new BigDecimal(100);
            BigDecimal sell = new BigDecimal(50);
            buyAndSell.put("buyAmout",buy);
            buyAndSell.put("sellAmout",sell);
        } else{*/
            // 筹码占比大于80%，买入2份，卖出1份
            BigDecimal buy = new BigDecimal(50);
            BigDecimal sell = new BigDecimal(100);
            buyAndSell.put("buyAmout",buy);
            buyAndSell.put("sellAmout",sell);
        //}
        log.info("买入数量比例{}卖出数量比例{}",buyAndSell.get("sellAmout"),buyAndSell.get("buyAmout"));
        return buyAndSell;
    }

    /**
     * » contract 	body 	string 	是 	合约标识
     * » size 	body 	integer(int64) 	是 	必选。交易数量，正数为买入，负数为卖出。平仓委托则设置为0。
     * » iceberg 	body 	integer(int64) 	否 	冰山委托显示数量。0为完全不隐藏。注意，隐藏部分成交按照taker收取手续费。
     * » price 	body 	string 	否 	委托价。价格为0并且tif为ioc，代表市价委托。
     * » close 	body 	boolean 	否 	设置为 true 的时候执行平仓操作，并且size应设置为0
     * » reduce_only 	body 	boolean 	否 	设置为 true 的时候，为只减仓委托
     * » tif 	body 	string 	否 	Time in force 策略，市价单当前只支持 ioc 模式
     * » text 	body 	string 	否 	订单自定义信息，用户可以用该字段设置自定义 ID，用户自定义字段必须满足以下条件：
     * » auto_size 	body 	string 	否 	双仓模式下用于设置平仓的方向，close_long 平多头， close_short 平空头，需要同时设置 size 为 0
     * » stp_act 	body 	string 	否 	Self-Trading Prevention Action,用户可以用该字段设置自定义限制自成交策略。
     * settle 	URL 	string 	是 	结算货币
     *
     * 合约交易下单
     *
     *     下单时指定的是合约张数 size ，而非币的数量，每一张合约对应的币的数量是合约详情接口里返回的 quanto_multiplier
     *     0 成交的订单在撤单 10 分钟之后无法再获取到，会提到订单不存在
     *     设置 reduce_only 为 true 可以防止在减仓的时候穿仓
     *     单仓模式下，如果需要平仓，需要设置 size 为 0 ，close 为 true
     *     双仓模式下，平仓需要使用 auto_size 来设置平仓方向，并同时设置 reduce_only 为 true，size 为 0
     *     设置 stp_act 决定使用限制用户自成交的策略，详细用法参考body参数stp_act
     */
    public  void FutureTrade() throws ApiException {
        // if testing against TestNet
        // set test contract

        String settle = "usdt";
        String contract = "SATS_USDT";
        String interval = "1h";



        // 设置你的API Key和Secret
        String apiKey = API_KEY;
        String apiSecret = SECRET_KEY;
        // 初始化ApiClient
        ApiClient client = new ApiClient();
        client.setApiKeySecret(apiKey, apiSecret);
        FuturesApi futuresApi = new FuturesApi(client);

        // update position leverage
        String leverage = "10";
        try {
            futuresApi.updatePositionLeverage(settle, contract, leverage, "");
        }catch (Exception e){
            log.info("捕捉到杠杆水平异常，需要关闭订单从新开始{}",e.getMessage());
            closeOrder(settle,contract);
        }


        // retrieve position information
        Long positionSize = 0L;
        try {
            Position position = futuresApi.getPosition(settle, contract);
            positionSize = position.getSize();
            assert positionSize != null;
        } catch (GateApiException e) {
            // ignore no position error
            if (!"POSITION_NOT_FOUND".equals(e.getErrorLabel())) {
                throw e;
            }
        }

        //获取预测做多和做空
        String predictInfo = getPredictInfo();
        if(!StringUtils.isEmpty(predictInfo)){
            if( (predictInfo.equals("Up")&&positionSize>0 ||(predictInfo.equals("Down")&&positionSize<0)) ){
                log.info("预测方向和持仓方向一致，不需要下单");
                return;
            }else{
                log.info("预测方向和持仓方向不一致，需要平仓再次下单");
                closeOrder(settle,contract);
            }
        }else{
            log.info("未获取到预测结果");
            return;
        }

        // set order size
        Contract futuresContract = futuresApi.getFuturesContract(settle, contract);
        Long orderSize = 8L;
        if (futuresContract.getOrderSizeMin() != null && futuresContract.getOrderSizeMin() > orderSize) {
            orderSize = futuresContract.getOrderSizeMin();
        }
        if (positionSize < 0) {
            // if short, set size to negative
            orderSize = -orderSize;
        }

        // example to update risk limit
        assert futuresContract.getRiskLimitBase() != null;
        assert futuresContract.getRiskLimitStep() != null;
        String riskLimit = new BigDecimal(futuresContract.getRiskLimitBase()).add(new BigDecimal(futuresContract.getRiskLimitStep()))
                .toPlainString();
        futuresApi.updatePositionRiskLimit(settle, contract, riskLimit);


        // retrieve last price to calculate margin needed
        List<FuturesTicker> tickers = futuresApi.listFuturesTickers(settle).contract(contract).execute();
        String lastPrice = tickers.get(0).getLast();
        assert lastPrice != null;
        log.info("last price of contract{}: {}", contract, lastPrice);
        MathContext mc = new MathContext(8, RoundingMode.UP);
        assert futuresContract.getQuantoMultiplier() != null;
        BigDecimal margin = new BigDecimal(orderSize).multiply(new BigDecimal(lastPrice), mc)
                .multiply(new BigDecimal(futuresContract.getQuantoMultiplier()), mc)
                .divide(new BigDecimal(leverage), mc)
                .multiply(new BigDecimal("1.1"));
        log.info("needs margin amount: {}", margin.toPlainString());

        // if balance not enough, transfer from spot account
        String available = "0";
        try {
            FuturesAccount futuresAccounts = futuresApi.listFuturesAccounts(settle);
            available = futuresAccounts.getAvailable();
            assert available != null;
        } catch (GateApiException e) {
            if (!"USER_NOT_FOUND".equals(e.getErrorLabel())) {
                throw e;
            }
        }
        log.info("Futures account available {} {}", available, settle.toUpperCase());
        if (new BigDecimal(available).compareTo(margin) < 0) {
            // make sure balance is enough
            Transfer transfer = new Transfer().amount(margin.toPlainString())
                    .currency(settle.toUpperCase())
                    .from(Transfer.FromEnum.SPOT)
                    .to(Transfer.ToEnum.FUTURES)
                    .settle(settle);
            WalletApi walletApi = new WalletApi(client);
            walletApi.transfer(transfer);
        }
        // example to cancel all open orders in contract
        futuresApi.cancelFuturesOrders(settle, contract, "");
        // order using market price
        FuturesOrder futuresOrder = new FuturesOrder();
        futuresOrder.setContract(contract);
        futuresOrder.setSize(predictInfo.equals("Up")?Math.abs(orderSize):-Math.abs(orderSize));
        futuresOrder.setPrice("0");
        futuresOrder.setTif(FuturesOrder.TifEnum.IOC);
        FuturesOrder orderResponse;
        try {
            orderResponse = futuresApi.createFuturesOrder(settle, futuresOrder);
        } catch (GateApiException e) {
            log.info("error encountered creating futures order: {}", e);
            return;
        }
        assert orderResponse.getId() != null;
        log.info("Order {} created with status:{}}", orderResponse.getId(), orderResponse.getStatus());

        if (orderResponse.getStatus() == FuturesOrder.StatusEnum.OPEN) {
            FuturesOrder order = futuresApi.getFuturesOrder(settle, orderResponse.getId().toString());
            assert order.getId() != null;
            log.info("Order {}} status {}}, total size {}}, left {}", order.getId(), order.getStatus(), order.getSize(),
                    order.getLeft());
            futuresApi.cancelFuturesOrder(settle, order.getId().toString());
            log.info("Order {} cancelled", order.getId());
        } else if (orderResponse.getStatus() == FuturesOrder.StatusEnum.FINISHED) {
            List<MyFuturesTrade> orderTrades = futuresApi.getMyTrades(settle).contract(contract).order(orderResponse.getId()).execute();
            assert orderTrades.size() > 0;
            long tradeSize = 0L;
            for (MyFuturesTrade t : orderTrades) {
                assert t.getOrderId() != null && t.getOrderId().equals(orderResponse.getId().toString());
                assert t.getSize() != null;
                tradeSize += t.getSize();
                log.info("Order {} filled size {} with price {}", t.getOrderId(), t.getSize(), t.getPrice());
            }
            assert tradeSize == orderSize;

            // example to update position margin
            String change = "0.01";
            futuresApi.updatePositionMargin(settle, contract, change);
        }
    }



    public static void closeOrder(String settle,String contract) throws ApiException {


        // 设置你的API Key和Secret
        String apiKey = API_KEY;
        String apiSecret = SECRET_KEY;
        // 初始化ApiClient
        ApiClient client = new ApiClient();
        client.setApiKeySecret(apiKey, apiSecret);
        FuturesApi futuresApi = new FuturesApi(client);

        Position position = futuresApi.getPosition(settle, contract);
        Long positionSize = position.getSize();
        if (positionSize > 0) {
            log.info("Currently holding a long position of size: {}" , positionSize);
        } else if (positionSize < 0) {
            log.info("Currently holding a short position of size:{} ", positionSize);
        } else {
            log.info("No open position found for the specified contract.");
            return;
        }

        FuturesOrder closeOrder = new FuturesOrder();
        closeOrder.setContract(contract); // 设置要平仓的合约
        closeOrder.setSize(0L);
        closeOrder.setPrice("0"); // 使用市场价平仓
        closeOrder.setTif(FuturesOrder.TifEnum.IOC); // 立即执行或取消（IOC）
        closeOrder.setClose(Boolean.TRUE);
        try {
            FuturesOrder orderResponse = futuresApi.createFuturesOrder("usdt", closeOrder);
            log.info("Close order created with status: {}" , orderResponse.getStatus());
        } catch (GateApiException e) {
            log.info("Error placing close order: {}" , e.getMessage());
        }
    }

    public String getPredictInfo(){
        // 定义请求的URL和参数
        String url = "http://localhost:8082/api/getPredictResult";
        String coin = "1000SATSUSDT";       // 你要查询的币种，例如 "BTC"
        String interval = "1h";    // 你要查询的时间周期，例如 "1h"
        // 构建请求URL，使用GET请求
        String requestUrl = url + "?coin=" + coin + "&interval=" + interval;
        // 发送GET请求
        HttpResponse response = HttpUtil.createGet(requestUrl).execute();

        // 获取响应状态码
        int statusCode = response.getStatus();
        if (statusCode == 200) {
            // 解析响应内容
            String body = response.body();
            JSONObject json = JSONUtil.parseObj(body);
            // 检查请求是否成功
            if (json.getBool("success")) {
                // 获取 PredictInfoDto 数据
                JSONObject predictInfoDtoJson = json.getJSONObject("data");
                PredictInfoDto predictInfoDto = JSONUtil.toBean(predictInfoDtoJson, PredictInfoDto.class);
                // 输出预测信息
                log.info("预测币种:{} " , predictInfoDto.getCurrencyName());
                log.info("预测周期:{} " , predictInfoDto.getPeriodDimension());
                log.info("预测收盘时间:{} " , predictInfoDto.getPredictClosetime());
                log.info("预测收盘价格: {}" ,predictInfoDto.getPredictCloseprice());
                return predictInfoDto.getPredictResult();
            } else {
                log.info("请求失败: {}" , json.getStr("message"));
            }
        } else {
            log.info("HTTP 请求失败，状态码:{} " , statusCode);
        }
        return StringUtils.EMPTY;
    }

}
